package org.sudoku.solver;

import static org.sudoku.solver.Sudoku.*;
import static org.sudoku.solver.Samurai.*;

/**
 * Solves a Samurai Sudoku using 9X9 Sudoku solvers.
 * 
 * @author shivkumar cm
 * 
 */
public class SamuraiSodokuSolver extends BaseSolver {

	/**
	 * Sudoku solvers
	 */
	protected I9X9Solver m_solvers[] = new I9X9Solver[GRIDS];

	public SamuraiSodokuSolver(I9X9Solver solvers[]) {
		m_solvers = solvers;
	}

	public void readProblem(String problem[]) {
		m_solvers[TOP_LT].readProblem(getSubProblem(problem, ROW_BASE[TOP_LT],
				COL_BASE[TOP_LT]));
		m_solvers[TOP_RT].readProblem(getSubProblem(problem, ROW_BASE[TOP_RT],
				COL_BASE[TOP_RT]));
		m_solvers[CENTRE].readProblem(getSubProblem(problem, ROW_BASE[CENTRE],
				COL_BASE[CENTRE]));
		m_solvers[BOT_LT].readProblem(getSubProblem(problem, ROW_BASE[BOT_LT],
				COL_BASE[BOT_LT]));
		m_solvers[BOT_RT].readProblem(getSubProblem(problem, ROW_BASE[BOT_RT],
				COL_BASE[BOT_RT]));
	}

	private void printCell(int solver, int row, int col) {
		if (m_solvers[solver].isKnown(row, col)) {
			System.out.print(" " + m_solvers[solver].get(row, col));
		} else {
			System.out.print(" _");
		}
		if (col % CUBE_SIZE == 0) {
			System.out.print(" |");
		}
	}

	private void printRow(int solver, int row) {
		for (int j = SUDOKU_BASE; j <= SUDOKU_MAX; ++j) {
			printCell(solver, row, j);
		}
	}

	private void printEmptyCubeRow() {
		for (int i = SUDOKU_BASE; i <= CUBE_SIZE; ++i) {
			System.out.print("  ");
		}
		System.out.print("  ");
	}

	private void printCubeRow(int solver, int row, int baseCol) {
		for (int j = baseCol; j < baseCol + CUBE_SIZE; ++j) {
			printCell(solver, row, j);
		}
	}

	public void prettyPrint() {
		for (int i = SUDOKU_BASE; i <= CUBE_SIZE * 2; ++i) {
			printRow(TOP_LT, i);
			printEmptyCubeRow();
			printRow(TOP_RT, i);
			System.out.println();
		}
		for (int i = CUBE_SIZE * 2 + 1; i <= SUDOKU_MAX; ++i) {
			printRow(TOP_LT, i);
			printCubeRow(CENTRE, i - CUBE_SIZE * 2, CUBE_SIZE + 1);
			printRow(TOP_RT, i);
			System.out.println();
		}
		for (int i = CUBE_SIZE + 1; i <= CUBE_SIZE * 2; ++i) {
			printEmptyCubeRow();
			printEmptyCubeRow();
			printRow(CENTRE, i);
			printEmptyCubeRow();
			printEmptyCubeRow();
			System.out.println();
		}
		for (int i = SUDOKU_BASE; i <= CUBE_SIZE; ++i) {
			printRow(BOT_LT, i);
			printCubeRow(CENTRE, i + CUBE_SIZE * 2, CUBE_SIZE + 1);
			printRow(BOT_RT, i);
			System.out.println();
		}
		for (int i = CUBE_SIZE + 1; i <= SUDOKU_MAX; ++i) {
			printRow(BOT_LT, i);
			printEmptyCubeRow();
			printRow(BOT_RT, i);
			System.out.println();
		}
	}

	private String[] getSubProblem(String baseProblem[], int fromRow,
			int fromCol) {
		String subProblem[] = new String[SUDOKU_MAX];
		for (int i = 0; i < SUDOKU_SIDE; ++i) {
			subProblem[i] = baseProblem[fromRow + i].substring(fromCol, fromCol
					+ SUDOKU_SIDE);
		}
		return subProblem;
	}

	/**
	 * @see ISolver#infer()
	 * 
	 * @return true if at least one of the sub-solvers progress
	 */
	public boolean infer() {
		boolean progressing = false;
		int grid = 1;
		for (I9X9Solver solver : m_solvers) {
			System.out.println("Grid - " + (grid++));
			if(!solver.solved())  {
				progressing |= solver.infer();
			}
		}
		return progressing;
	}

	public void applyInferences() {
		int grid = 1;
		for (I9X9Solver solver : m_solvers) {
			System.out.print("Grid - " + (grid++) + ": ");
			solver.applyInferences();
		}
		crossApply();
	}

	/**
	 * Cross-applies the inferences in the cubes that overlap. For example, if
	 * an inference in found in the lowest-rightmost cube of the top-left grid
	 * then it should be copied over to the corresponding cube in the centre
	 * grid.
	 */
	private void crossApply() {

		System.out.println("Cross applying...");
		for (int triplet[] : m_solvers[TOP_LT].getInferences()) {
			int row = triplet[ROW], col = triplet[COL], digit = triplet[DIGIT];
			if (row > CUBE_SIZE * 2 && col > CUBE_SIZE * 2) {
				m_solvers[CENTRE].set(row - CUBE_SIZE * 2, col - CUBE_SIZE * 2,
						digit);
			}
		}
		for (int triplet[] : m_solvers[TOP_RT].getInferences()) {
			int row = triplet[ROW], col = triplet[COL], digit = triplet[DIGIT];
			if (row > CUBE_SIZE * 2 && col <= CUBE_SIZE) {
				m_solvers[2].set(row - CUBE_SIZE * 2, col + CUBE_SIZE * 2,
						digit);
			}
		}
		for (int triplet[] : m_solvers[CENTRE].getInferences()) {
			int row = triplet[ROW], col = triplet[COL], digit = triplet[DIGIT];
			if (row <= CUBE_SIZE && col <= CUBE_SIZE) {
				m_solvers[TOP_LT].set(row + CUBE_SIZE * 2, col + CUBE_SIZE * 2,
						digit);
			}
			if (row <= CUBE_SIZE && col > CUBE_SIZE * 2) {
				m_solvers[TOP_RT].set(row + CUBE_SIZE * 2, col - CUBE_SIZE * 2,
						digit);
			}
			if (row > CUBE_SIZE * 2 && col <= CUBE_SIZE) {
				m_solvers[BOT_LT].set(row - CUBE_SIZE * 2, col + CUBE_SIZE * 2,
						digit);
			}
			if (row > CUBE_SIZE * 2 && col > CUBE_SIZE * 2) {
				m_solvers[BOT_RT].set(row - CUBE_SIZE * 2, col - CUBE_SIZE * 2,
						digit);
			}
		}
		for (int triplet[] : m_solvers[BOT_LT].getInferences()) {
			int row = triplet[ROW], col = triplet[COL], digit = triplet[DIGIT];
			if (row <= CUBE_SIZE && col > CUBE_SIZE * 2) {
				m_solvers[CENTRE].set(row + CUBE_SIZE * 2, col - CUBE_SIZE * 2,
						digit);
			}
		}
		for (int triplet[] : m_solvers[BOT_RT].getInferences()) {
			int row = triplet[ROW], col = triplet[COL], digit = triplet[DIGIT];
			if (row <= CUBE_SIZE && col <= CUBE_SIZE) {
				m_solvers[CENTRE].set(row + CUBE_SIZE * 2, col + CUBE_SIZE * 2,
						digit);
			}
		}
	}

	public boolean solved() {
		for (I9X9Solver solver : m_solvers) {
			if (!solver.solved()) {
				return false;
			}
		}
		return true;
	}

	public void set(int row, int col, int digit) {
		throw new UnsupportedOperationException(MSG_UNSUPPORTED_FOR_SAMURAI);
	}

	public int get(int row, int col) {
		throw new UnsupportedOperationException(MSG_UNSUPPORTED_FOR_SAMURAI);
	}

	public boolean isKnown(int row, int col) {
		throw new UnsupportedOperationException(MSG_UNSUPPORTED_FOR_SAMURAI);
	}

	public int[][] getInferences() {
		throw new UnsupportedOperationException(MSG_UNSUPPORTED_FOR_SAMURAI);
	}
}
